package service

import (
	"context"
	"happy-sudoku/internal/manager"
	"happy-sudoku/internal/model"
	"happy-sudoku/internal/repository"
	"happy-sudoku/internal/util"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// IPlayerService is to be implemented by PlayerService
type IPlayerService interface {
	// GetOne gets one player
	GetOne(ctx context.Context, playerID int) (*model.Player, error)
	// GetList gets list of players
	GetList(ctx context.Context, playersID []int) ([]*model.Player, error)
	// GetAll gets all the player
	GetAll(ctx context.Context) ([]*model.Player, error)
	// Create creates a player
	Create(ctx context.Context, player model.Player) (*model.Player, error)
	// Delete a player (unregister)
	Delete(ctx context.Context, playerID int) error
	// Login logins player
	Login(ctx context.Context, playerToken string) (*model.Player, error)
	// Logout player logouts
	Logout(ctx context.Context, playerID int) error
	// Register registers a player
	Register(ctx context.Context, input model.RegisterInput) (*model.Player, error)
	// GetPlayerState gets player state
	GetPlayerState(ctx context.Context, playerID int) (*model.PlayerState, error)
	// UpdatePlayerState updates player state
	UpdatePlayerState(ctx context.Context, playerID int, state model.PlayerState) (*model.PlayerState, error)
	// UpdatePlayerProfile updates player profile
	UpdatePlayerProfile(ctx context.Context, playerID int, player model.Player) (*model.Player, error)
	// GetCharacterByCharID get character by characterID
	GetCharacterByCharID(ctx context.Context, charID int) (*model.CharacterData, error)
	// UpdateCharacter updates player character
	UpdateCharacter(ctx context.Context, playerID int, character model.CharacterData) (*model.CharacterData, error)
}

// PlayerService implements IPlayerService
type PlayerService struct {
	playerRepo repository.IPlayerRepo
	authRepo   repository.IAuthRepo
	charRepo   repository.ICharacterDataRepo
	mgr        manager.IPlayerManager
	roomMgr    manager.IRoomManager
}

// PlayerServiceConfig use to new PlayerSerivce
type PlayerServiceConfig struct {
	PlayerRepo repository.IPlayerRepo
	AuthRepo   repository.IAuthRepo
	CharRepo   repository.ICharacterDataRepo
	Mgr        manager.IPlayerManager
	RoomMgr    manager.IRoomManager
}

const playerServiceErrHeader = "PlayerSerivce."

// NewPlayerService new PlayerService
func NewPlayerService(cfg PlayerServiceConfig) *PlayerService {
	return &PlayerService{
		playerRepo: cfg.PlayerRepo,
		authRepo:   cfg.AuthRepo,
		charRepo:   cfg.CharRepo,
		mgr:        cfg.Mgr,
		roomMgr:    cfg.RoomMgr,
	}
}

// GetOne gets one player
func (p *PlayerService) GetOne(ctx context.Context, playerID int) (*model.Player, error) {
	return p.playerRepo.GetOne(playerID)
}

// GetAll gets all the player
func (p *PlayerService) GetAll(ctx context.Context) ([]*model.Player, error) {
	return p.playerRepo.GetAll()
}

// GetList gets list of players
func (p *PlayerService) GetList(ctx context.Context, playersID []int) ([]*model.Player, error) {
	return p.playerRepo.GetList(playersID)
}

// Create creates a player
func (p *PlayerService) Create(ctx context.Context, player model.Player) (*model.Player, error) {
	return p.playerRepo.Create(player)
}

// Delete a player (unregister)
func (p *PlayerService) Delete(ctx context.Context, playerID int) error {
	return p.playerRepo.Delete(playerID)
}

// Login logins player
func (p *PlayerService) Login(ctx context.Context, playerToken string) (*model.Player, error) {
	player, err := p.authRepo.GetPlayerByToken(playerToken)
	if err != nil {
		return nil, err
	}
	state := model.NewPlayerStateByDefault(player.ID)
	state.Status = model.StatusWandering
	// add to state
	state, err = p.mgr.UpdatePlayerState(player.ID, *state)
	player.State = *state

	return player, nil
}

// Logout player logouts
func (p *PlayerService) Logout(ctx context.Context, playerID int) error {
	const errHeader = playerServiceErrHeader + "Logout"

	err := p.roomMgr.HandleLeaveRoom(ctx, playerID)
	// TODO: Find a better way
	// Log, but not return
	if err != nil {
		logrus.Warn(errors.Wrap(err, errHeader).Error())
	}
	p.roomMgr.UnsubscribeRoomList(ctx, playerID)

	// Clear Player State
	err = p.mgr.DeletePlayerState(playerID)
	// TODO: Find a better way
	// Log, but not return
	if err != nil {
		logrus.Warn(errors.Wrap(err, errHeader).Error())
	}

	return nil
}

// Register registers a player
func (p *PlayerService) Register(ctx context.Context, input model.RegisterInput) (*model.Player, error) {
	// create character
	character := model.NewCharacterData()
	character.ImgURL = input.CharacterImageURL
	// set to repo
	newCharacter, err := p.charRepo.Create(*character)
	if err != nil {
		return nil, err
	}

	// create player
	player := model.NewPlayer()
	player.Name = input.PlayerName
	player.CharacterID = newCharacter.ID
	player.Token = util.NewUUIDString()
	player, err = p.playerRepo.Create(*player)
	if err != nil {
		return nil, err
	}

	// Add player state
	state := model.NewPlayerStateByDefault(player.ID)
	state.Status = model.StatusWandering
	// add to state
	state, err = p.mgr.UpdatePlayerState(player.ID, *state)
	player.State = *state

	return player, nil
}

// GetPlayerState gets player state
func (p *PlayerService) GetPlayerState(ctx context.Context, playerID int) (*model.PlayerState, error) {
	return p.mgr.GetPlayerState(playerID)
}

// UpdatePlayerState updates player state
func (p *PlayerService) UpdatePlayerState(ctx context.Context, playerID int, state model.PlayerState) (*model.PlayerState, error) {
	newState, err := p.mgr.UpdatePlayerState(playerID, state)
	if err != nil {
		return nil, err
	}
	return newState, nil
}

// UpdatePlayerProfile updates player profile
func (p *PlayerService) UpdatePlayerProfile(ctx context.Context, playerID int, player model.Player) (*model.Player, error) {
	player.ID = playerID
	newPlayer, err := p.playerRepo.Update(player)
	if err != nil {
		return nil, err
	}
	return newPlayer, nil
}

// GetCharacterByCharID get character by characterID
func (p *PlayerService) GetCharacterByCharID(ctx context.Context, charID int) (*model.CharacterData, error) {
	return p.charRepo.GetOne(charID)
}

// UpdateCharacter updates player character
func (p *PlayerService) UpdateCharacter(ctx context.Context, playerID int, character model.CharacterData) (*model.CharacterData, error) {
	player, err := p.playerRepo.GetOne(playerID)
	if err != nil {
		return nil, err
	}

	character.ID = player.CharacterID
	newCharacter, err := p.charRepo.Update(character)
	return newCharacter, nil
}
